package org.appleframework.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Java utils
 */
@SuppressWarnings("unchecked")
public class Java {

  /**
   * Find the first public static method
   * @param name The method name
   * @param clazz The class
   * @return The method or null
   */
  public static Method findActionMethod(String name, Class clazz) {
    while (!clazz.getName().equals("java.lang.Object")) {
      for (Method m : clazz.getDeclaredMethods()) {
        if (m.getName().equalsIgnoreCase(name) && Modifier.isPublic(m.getModifiers()) && Modifier.isStatic(m.getModifiers())) {
          return m;
        }
      }
      clazz = clazz.getSuperclass();
    }
    return null;
  }

  /**
   * Invoke a static method
   * @param clazz The class
   * @param method The method name
   * @return The result
   * @throws java.lang.Exception
   */
  public static Object invokeStatic(Class clazz, String method) throws Exception {
    return invokeStatic(clazz, method, new Object[0]);
  }

  /**
   * Invoke a static method with args
   * @param clazz The class
   * @param method The method name
   * @param args Arguments
   * @return The result
   * @throws java.lang.Exception
   */
  public static Object invokeStatic(Class clazz, String method, Object... args) throws Exception {
    Class[] types = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
      types[0] = args[0].getClass();
    }
    Method m = clazz.getDeclaredMethod(method, types);
    return m.invoke(null, args);
  }

  public static Object invokeStatic(Method method, Object[] args) throws Exception {
    return method.invoke(null, args);
  }

  public static String rawMethodSignature(Method method) {
    StringBuilder sig = new StringBuilder();
    sig.append(method.getDeclaringClass().getName());
    sig.append(".");
    sig.append(method.getName());
    sig.append('(');
    for (Class clazz : method.getParameterTypes()) {
      sig.append(rawJavaType(clazz));
    }
    sig.append(")");
    sig.append(rawJavaType(method.getReturnType()));
    return sig.toString();
  }

  public static String rawJavaType(Class clazz) {
    if (clazz.getName().equals("void")) {
      return "V";
    }
    if (clazz.getName().equals("boolean")) {
      return "Z";
    }
    if (clazz.getName().equals("byte")) {
      return "B";
    }
    if (clazz.getName().equals("char")) {
      return "C";
    }
    if (clazz.getName().equals("double")) {
      return "D";
    }
    if (clazz.getName().equals("float")) {
      return "F";
    }
    if (clazz.getName().equals("int")) {
      return "I";
    }
    if (clazz.getName().equals("long")) {
      return "J";
    }
    if (clazz.getName().equals("short")) {
      return "S";
    }
    if (clazz.getName().startsWith("[")) {
      return clazz.getName().replace('.', '/');
    }
    return "L" + (clazz.getName().replace('.', '/')) + ";";
  }

  /**
   * Find all annotated method from a class
   * @param clazz The class
   * @param annotationType The annotation class
   * @return A list of method object
   */
  public static List<Method> findAllAnnotatedMethods(Class clazz, Class annotationType) {
    List<Method> methods = new ArrayList<Method>();
    while (!clazz.equals(Object.class)) {
      for (Method method : clazz.getDeclaredMethods()) {
        if (method.isAnnotationPresent(annotationType)) {
          methods.add(method);
        }
      }
      clazz = clazz.getSuperclass();
    }
    return methods;
  }

  public static void findAllFields(Class clazz, Set<Field> found) {
    Field[] fields = clazz.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
      found.add(fields[i]);
    }
    Class sClazz = clazz.getSuperclass();
    if (sClazz != null && sClazz != Object.class) {
      findAllFields(sClazz, found);
    }
  }
}
